package controllers

import akka.actor.Actor
import play.api.Logger
import akka.util.duration._
import play.api.libs.concurrent.Akka
import play.api.Play.current
import akka.actor.Props
import transcoding._
import java.util.concurrent.ConcurrentHashMap
import scala.collection.JavaConversions._
import java.util.concurrent.atomic.AtomicReference
import java.io.File
import scala.Some

object LibraryManager {

  class LibraryManagerActor extends Actor {

    def receive = {
      case 'Update =>
        Logger.debug("Updating Library...")
        mediaLibrary.lazySet(updateLibrary(getLibraryDir))
        Logger.debug("...DONE updating library")

      case message => Logger.debug("Could not understand: " + message.toString)
    }
  }

  //TODO use cache (with different namespace) to store this
  private val mediaLibrary = new AtomicReference[ConcurrentHashMap[transcoding.MediaType, ConcurrentHashMap[Int, transcoding.MediaFileManager]]]

  def getLibraryDir = {
    "/home/giacomo/Videos/"
  }

  def getAltLibraryDir = {
    "/home/giacomo/Videos/.blade/altMedia/"
  }

  def getMediaLibrary = {
    val l = this.mediaLibrary.get
    if (l == null) None
    else Some(l)
  }

  val actor = Akka.system.actorOf(Props[LibraryManagerActor], name = "fileManagerActor")
  Logger.error(actor.path.toString)
  Akka.system.scheduler.schedule(0 seconds, 35 minutes, actor, 'Update)

  def updateLibrary(dirPath: String) = {
    val mediaDir = new java.io.File(dirPath)
    val altMediaDir = new java.io.File(getAltLibraryDir)

    if (!mediaDir.isDirectory())
      throw new IllegalArgumentException("The media path '%s' is not pointing to a directory!".format(mediaDir.getAbsolutePath))
    if (!altMediaDir.isDirectory())
      throw new IllegalArgumentException("The media path '%s' is not pointing to a directory!".format(altMediaDir.getAbsolutePath))

    val library = new ConcurrentHashMap[transcoding.MediaType, ConcurrentHashMap[Int, transcoding.MediaFileManager]]
    addAllMediaFiles(mediaDir)(library)
    addAlternativeFormats(altMediaDir)(library)

    library
  }

  def onboard() = {
    import transcoding.TranscodeProcessManager.ifNotNull
    getMediaLibrary.map(
      library =>
        ifNotNull(library.get(Video)) {
          videos => {
            videos.values/*.filter(mfm => !mfm.getMediaFileWithMaxBitRateAndFormat(None, None, FlashPlayer).isDefined).*/foreach(
              mfm => TranscodeProcessManager.startInitialProcess(mfm)
              )
          }
          None
        }
    )
    match {
      case None => throw new RuntimeException("No media library found!")
      case _ =>
    }
  }

  def onboard(files: scala.collection.Iterable[File]) = {
    getMediaLibrary.map(
      library =>
        files.map(
          f => {
            Logger.debug("adding %s" format f.getAbsolutePath)

            val mf = transcoding.MediaFile(f)
            val mfm = add(mf.mediaType, mf, library)

            mf.mediaType match {
              case transcoding.Video =>
                TranscodeProcessManager.startInitialProcess(mfm)
            }
          }
        )
    ) match {
      case None => throw new RuntimeException("No media library found!")
      case _ =>
    }
  }

  def getMediaFile(fileID: Int, library: Option[ConcurrentHashMap[transcoding.MediaType, ConcurrentHashMap[Int, transcoding.MediaFileManager]]] = getMediaLibrary) = {

    def getMF(library: ConcurrentHashMap[transcoding.MediaType, ConcurrentHashMap[Int, transcoding.MediaFileManager]]): Option[transcoding.MediaFileManager] = {
      for (entry <- library.entrySet) {
        entry.getKey match {
          case _ =>
            val mfm = entry.getValue.get(fileID)
            if (mfm != null) return Some(mfm)
        }
      }
      return None
    }

    library.flatMap(l => getMF(l))
  }

  def addAlternativeFormat(origMF: transcoding.MediaFile, altMediaFile: java.io.File): Unit = {
    addAlternativeFormat(origMF.filePath, transcoding.MediaFile(altMediaFile))
  }

  def addAlternativeFormat(originalFilePath: String, altMediaFile: transcoding.MediaFile,
          library: Option[ConcurrentHashMap[transcoding.MediaType, ConcurrentHashMap[Int, transcoding.MediaFileManager]]] = getMediaLibrary): Unit = {
    getMediaFile(originalFilePath.hashCode, library) match {
      case Some(mfm) => mfm.addOtherVersion(altMediaFile)
      //getMediaLibrary.map(l => l.get(mfm.mainMediaFile.mediaType).put(origMF.filePath.hashCode, mfm))
      case _ => throw new RuntimeException("Could not find %s in MediaLibrary".format(originalFilePath))
    }
  }

  private def addAlternativeFormats(altMediaDir: java.io.File)(library: ConcurrentHashMap[transcoding.MediaType, ConcurrentHashMap[Int, transcoding.MediaFileManager]]): Unit = {
    altMediaDir.listFiles()
      .foreach(f =>
      if (f.isDirectory()) addAlternativeFormats(f)(library)
      else {
        try {
          val mf: transcoding.MediaFile = transcoding.MediaFile(f)
          mf.mediaType match {
            case transcoding.Image =>
            case transcoding.Other =>
            case media =>
              val originalFilePath = mf.metaData.map(md => md.getValue("grouping").trim).getOrElse("")
              if (!originalFilePath.isEmpty()) {
                Logger.debug("*******Found alt version for " + originalFilePath + "mediaType = " + media)
                addAlternativeFormat(originalFilePath, mf, Some(library))

              } //else f.delete
          }
        } catch {
          case error => Logger.error("*******WHILE ADDING ALT!", error)
        }
      })
  }

  private def add(mediaType: MediaType, mf: MediaFile, library: ConcurrentHashMap[transcoding.MediaType, ConcurrentHashMap[Int, transcoding.MediaFileManager]]): MediaFileManager = {

    val mfm: MediaFileManager = new transcoding.MediaFileManager(mf)
    library.getOrElseUpdate(mediaType, new ConcurrentHashMap[Int, transcoding.MediaFileManager]).put(mf.filePath.hashCode, mfm)

    mfm
  }

  private def addAllMediaFiles(mediaDir: java.io.File)(library: ConcurrentHashMap[transcoding.MediaType, ConcurrentHashMap[Int, transcoding.MediaFileManager]]): Unit = {
    mediaDir.listFiles(new java.io.FileFilter {
      override def accept(fileName: java.io.File) = {
        !fileName.isHidden
      }
    })
      .foreach(f =>
      if (f.isDirectory()) addAllMediaFiles(f)(library)
      else {
        try {
          val mf: transcoding.MediaFile = transcoding.MediaFile(f)
          mf.mediaType match {
            case transcoding.Image =>
            case transcoding.Other =>
            case media => add(media, mf, library); ()
          }
        }
        catch {
          case _ => ()
        }
      }
    )
  }
}